﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using Point2D = System.Windows.Point;
using MediaColor = System.Windows.Media.Color;

namespace UI.Control
{

    sealed public class Cover : ModelVisual3D
    {

        #region Fields

        private CoverFlow3D _coverFlow;

        // The content
        private readonly Model3DGroup _modelGroup = new Model3DGroup();

        // It is the index of this cover in the collection
        private int _collectionIndex;

        // It is the index of this cover visually
        private int _visualIndex;

        // Define if it is selected
        private bool _isSelected;

        // Transformation on the cover
        private readonly AxisAngleRotation3D _rotation;
        private readonly TranslateTransform3D _translation;

        #endregion

        #region Constructor

        public Cover(CoverFlow3D coverFlow, int collectionIndex, int visualIndex, UIElement content)
        {
            Content = _modelGroup;

            _coverFlow = coverFlow;
            _collectionIndex = collectionIndex;
            _visualIndex = visualIndex;

            // The cover
            Material material = LoadMaterial(content);
            _modelGroup.Children.Add(new GeometryModel3D(Tessellate(), material));

            // The mirror
            material = LoadMaterialMirror(content);
            _modelGroup.Children.Add(new GeometryModel3D(TessellateMirror(), material));

            // The transformation to move the cover
            _rotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), CalculateRotationAngle(_visualIndex));
            _translation = new TranslateTransform3D(CalculateTranslationX(_visualIndex), 0, CalculateTranslationZ(_visualIndex));

            var transformGroup = new Transform3DGroup();
            transformGroup.Children.Add(new RotateTransform3D(_rotation));
            transformGroup.Children.Add(_translation);
            _modelGroup.Transform = transformGroup;
        }

        #endregion

        #region Properties

        public int CollectionIndex
        {
            get
            {
                return _collectionIndex;
            }
        }

        public int VisualIndex
        {
            get
            {
                return _visualIndex;
            }
        }

        public CoverFlow3D CoverFlow
        {
            get
            {
                return _coverFlow;
            }
        }

        public bool IsSelected
        {
            set
            {
                if (_isSelected == value)
                    return;

                _isSelected = value;

                OnUpdateContent(_coverFlow._dataView.GetItemAt(CollectionIndex));
            }
        }

        #endregion

        #region Tessellate

        private Geometry3D Tessellate()
        {
            double dx = RectangleDx();
            double dy = RectangleDy();

            var p0 = new Point3D((-1 + dx) * _coverFlow.ItemSizeFactor, (-1 + dy) * _coverFlow.ItemSizeFactor, 0);
            var p1 = new Point3D((1 - dx) * _coverFlow.ItemSizeFactor, (-1 + dy) * _coverFlow.ItemSizeFactor, 0);
            var p2 = new Point3D((1 - dx) * _coverFlow.ItemSizeFactor, (1 - dy) * _coverFlow.ItemSizeFactor, 0);
            var p3 = new Point3D((-1 + dx) * _coverFlow.ItemSizeFactor, (1 - dy) * _coverFlow.ItemSizeFactor, 0);

            var q0 = new Point2D(0, 0);
            var q1 = new Point2D(1, 0);
            var q2 = new Point2D(1, 1);
            var q3 = new Point2D(0, 1);

            return BuildMesh(p0, p1, p2, p3, q0, q1, q2, q3);
        }

        #endregion

        #region TessellateMirror

        private Geometry3D TessellateMirror()
        {
            double dx = RectangleDx();
            double dy = RectangleDy();

            var p0 = new Point3D((-1 + dx) * _coverFlow.ItemSizeFactor, (-3 + 3 * dy) * _coverFlow.ItemSizeFactor, 0);
            var p1 = new Point3D((1 - dx) * _coverFlow.ItemSizeFactor, (-3 + 3 * dy) * _coverFlow.ItemSizeFactor, 0);
            var p2 = new Point3D((1 - dx) * _coverFlow.ItemSizeFactor, (-1 + dy) * _coverFlow.ItemSizeFactor, 0);
            var p3 = new Point3D((-1 + dx) * _coverFlow.ItemSizeFactor, (-1 + dy) * _coverFlow.ItemSizeFactor, 0);

            var q0 = new Point2D(0, 1);
            var q1 = new Point2D(1, 1);
            var q2 = new Point2D(1, 0);
            var q3 = new Point2D(0, 0);

            return BuildMesh(p0, p1, p2, p3, q0, q1, q2, q3);
        }

        #endregion

        #region Materials

        private ImageSource LoadImageSource(string imagePath)
        {
            System.Drawing.Image thumb = System.Drawing.Image.FromFile(imagePath);
            return new BitmapImage(new Uri(imagePath, UriKind.RelativeOrAbsolute));
        }

        private Material LoadMaterial(UIElement content)
        {
            VisualBrush  brush = new VisualBrush(content);

            // Optimization
            brush.SetValue(RenderOptions.CachingHintProperty, CachingHint.Cache);

            return new DiffuseMaterial(new VisualBrush(content));
        }

        /// <summary>
        /// For the texture, we use a VisualBrush object and we apply a LinearGradientBrush to the image OpacityMask.
        /// </summary>
        private DiffuseMaterial LoadMaterialMirror(UIElement content)
        {
            MediaColor startColor = MediaColor.FromArgb(0, 255, 255, 255);
            MediaColor endColor = MediaColor.FromArgb(127, 0, 0, 0);
            
            StackPanel panel = new StackPanel();
            panel.Children.Add(content);

            panel.OpacityMask = new LinearGradientBrush(startColor, endColor, new Point(0, 0.5), new Point(0, 1));

            var brush = new VisualBrush(panel);

            return new DiffuseMaterial(brush);
        }

        #endregion

        #region Position calculation

        private double CalculateRotationAngle(int visualIndex)
        {
            return _coverFlow.Layout.CalculateRotationAngle(visualIndex, this);
        }

        private double CalculateTranslationX(int visualIndex)
        {
            return _coverFlow.Layout.CalculateTranslationX(visualIndex, this) * _coverFlow.ItemSizeFactor;
        }

        //private double CalculateTranslationY(int visualIndex)
        //{
        //    return _coverFlow.Layout.CalculateTranslationY(visualIndex, this) * _coverFlow.ItemSizeFactor;
        //}

        private double CalculateTranslationZ(int visualIndex)
        {
            return _coverFlow.Layout.CalculateTranslationZ(visualIndex, this) * _coverFlow.ItemSizeFactor;
        }

        #endregion

        #region OnUpdateVisualIndex

        /// <summary>
        /// This method is called when the control scroll to
        /// update the index of this cover.
        /// </summary>
        internal void OnUpdateVisualIndex(int visualIndexDelta)
        {
            _visualIndex = _visualIndex + visualIndexDelta;

            Animate(_visualIndex);
        }

        #endregion

        #region OnUpdateCollectionIndex

        internal void OnUpdateCollectionIndex(int collectionIndex)
        {
            _collectionIndex = collectionIndex;
        }

        #endregion

        #region OnLayoutPropertyChanged

        internal void OnLayoutPropertyChanged()
        {
            Animate(VisualIndex);
        }

        #endregion

        #region OnUpdateContent

        internal void OnUpdateContent(object data)
        {
            UIElement element = _coverFlow.GenerateContent(data, _isSelected);

            ((GeometryModel3D)_modelGroup.Children[0]).Material = LoadMaterial(element);
            ((GeometryModel3D)_modelGroup.Children[1]).Material = LoadMaterialMirror(element);
        }

        #endregion

        #region Animate

        private void Animate(int visualIndex)
        {
            TimeSpan duration = _coverFlow.AnimationTime;

            var rotateAnimation = new DoubleAnimation(CalculateRotationAngle(visualIndex), duration);
            var xAnimation = new DoubleAnimation(CalculateTranslationX(visualIndex), duration);
            //var yAnimation = new DoubleAnimation(CalculateTranslationY(visualIndex), duration);
            var zAnimation = new DoubleAnimation(CalculateTranslationZ(visualIndex), duration);

            rotateAnimation.DecelerationRatio = 0.8;
            zAnimation.DecelerationRatio = 0.8;
            //yAnimation.DecelerationRatio = 0.8;
            xAnimation.DecelerationRatio = 0.8;

            _rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, rotateAnimation);
            _translation.BeginAnimation(TranslateTransform3D.OffsetXProperty, xAnimation);
            //_translation.BeginAnimation(TranslateTransform3D.OffsetYProperty, yAnimation);
            _translation.BeginAnimation(TranslateTransform3D.OffsetZProperty, zAnimation);
        }

        #endregion

        #region Helpers

        private MeshGeometry3D BuildMesh(
            Point3D p0,
            Point3D p1,
            Point3D p2,
            Point3D p3,
            Point2D q0,
            Point2D q1,
            Point2D q2,
            Point2D q3)
        {
            var mesh = new MeshGeometry3D();
            mesh.Positions.Add(p0);
            mesh.Positions.Add(p1);
            mesh.Positions.Add(p2);
            mesh.Positions.Add(p3);

            var normal = CalculateNormal(p0, p1, p2);
            mesh.TriangleIndices.Add(0);
            mesh.TriangleIndices.Add(1);
            mesh.TriangleIndices.Add(2);
            mesh.Normals.Add(normal);
            mesh.Normals.Add(normal);
            mesh.Normals.Add(normal);
            mesh.TextureCoordinates.Add(q3);
            mesh.TextureCoordinates.Add(q2);
            mesh.TextureCoordinates.Add(q1);

            normal = CalculateNormal(p2, p3, p0);
            mesh.TriangleIndices.Add(2);
            mesh.TriangleIndices.Add(3);
            mesh.TriangleIndices.Add(0);
            mesh.Normals.Add(normal);
            mesh.Normals.Add(normal);
            mesh.Normals.Add(normal);
            mesh.TextureCoordinates.Add(q0);
            mesh.TextureCoordinates.Add(q1);
            mesh.TextureCoordinates.Add(q2);

            mesh.Freeze();
            return mesh;
        }

        private Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
        {
            var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
            var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);

            return Vector3D.CrossProduct(v0, v1);
        }

        internal bool Matches(MeshGeometry3D mesh)
        {
            foreach (GeometryModel3D part in _modelGroup.Children)
                if (part.Geometry == mesh)
                    return true;

            return false;
        }

        private double RectangleDx()
        {
            //if (_imageSource.Width > _imageSource.Height)
            //    return 0;

            //return 1 - _imageSource.Width / _imageSource.Height;
            return 0;
        }

        private double RectangleDy()
        {
            //if (_imageSource.Width > _imageSource.Height)
            //    return 1 - _imageSource.Height / _imageSource.Width;

            return 0;
        }

        #endregion

    }
}
